package twel.probenprophet.domain.rules.checks;

import twel.probenprophet.base.utility.IntTriple;
import twel.probenprophet.base.utility.MathHelper;

public class CheckDeciderImpl implements CheckDecider {

	@Override
	public CheckResult determineResult(Check check, IntTriple dice) {
		if (check == null || dice == null) {
			throw new IllegalArgumentException("arguments must not be null");
		}

		return calculateResult(check, dice);
	}

	private CheckResult calculateResult(Check check, IntTriple dice) {

		int points = getPoints(check);

		CheckOutcome specialSuccess = determineSpecialCheckOutcome(check, dice);
		if (specialSuccess != null) {
			return buildSpecialSuccessResult(check, specialSuccess);
		}

		if (points > 0) {
			return buildResultWithPoints(check, dice, points);
		} else {
			return buildResultWithoutPoints(check, dice, points);
		}
	}

	private int getPoints(Check check) {
		int value = check.getValue();
		int difficulty = check.getDifficulty();

		if (value < 0) {
			difficulty -= value;
			value = 0;
		}

		return value - difficulty;
	}

	private CheckResult buildResultWithPoints(Check check, IntTriple dice, int points) {
		IntTriple attributeTriple = new IntTriple(check.getAttribute1(), check.getAttribute2(), check.getAttribute3());

		int leftoverPoints = getLeftoverPoints(dice, attributeTriple, points);

		if (leftoverPoints >= 0) {
			int leftoverValue = Math.min(leftoverPoints, check.getValue());
			int quality = Math.max(leftoverValue, getMinimumEffect(check));
			int possibleDifficulty = leftoverPoints + getPossibleDifficultyViaAttributes(dice, attributeTriple);

			return new CheckResult(CheckOutcome.SUCCESSFUL, quality, possibleDifficulty);
		} else {
			return new CheckResult(CheckOutcome.UNSUCCESSFUL, null, leftoverPoints);
		}
	}

	private int getLeftoverPoints(IntTriple dice, IntTriple attributeTriple, int points) {
		int leftoverPoints = points;

		for (int i = 0; i <= 2; i++) {
			int die = dice.get(i);
			int attribute = attributeTriple.get(i);

			if (die > attribute) {
				leftoverPoints -= die - attribute;
			}
		}

		return leftoverPoints;
	}

	private CheckResult buildResultWithoutPoints(Check check, IntTriple dice, int points) {

		IntTriple attributeTriple = new IntTriple(check.getAttribute1(), check.getAttribute2(), check.getAttribute3());
		attributeTriple.increaseAllBy(points);

		if (anyRollExceedsAttributeValue(dice, attributeTriple)) {
			return buildUnsuccessfulResultWithoutPoints(dice, attributeTriple);
		} else {
			return buildSuccessfulResultWithoutPoints(check, dice, attributeTriple);
		}
	}

	private boolean anyRollExceedsAttributeValue(IntTriple dice, IntTriple attributeTriple) {
		return dice.getFirst() > attributeTriple.getFirst()
				|| dice.getSecond() > attributeTriple.getSecond()
				|| dice.getThird() > attributeTriple.getThird();
	}

	private CheckResult buildUnsuccessfulResultWithoutPoints(IntTriple dice, IntTriple attributeTriple) {

		int negativePossibleDifficulty = 0;
		negativePossibleDifficulty -= getNegativePossibleDifficulty(dice.getFirst(), attributeTriple.getFirst());
		negativePossibleDifficulty -= getNegativePossibleDifficulty(dice.getSecond(), attributeTriple.getSecond());
		negativePossibleDifficulty -= getNegativePossibleDifficulty(dice.getThird(), attributeTriple.getThird());

		return new CheckResult(CheckOutcome.UNSUCCESSFUL, null, negativePossibleDifficulty);
	}

	private int getNegativePossibleDifficulty(int die, int attribute) {
		return die > attribute ? die - attribute : 0;
	}

	private CheckResult buildSuccessfulResultWithoutPoints(Check check, IntTriple dice, IntTriple reducedAttriuteTriple) {

		int minimumQuality = getMinimumEffect(check);
		int possibleDifficulty = getPossibleDifficultyViaAttributes(dice, reducedAttriuteTriple);

		return new CheckResult(CheckOutcome.SUCCESSFUL, minimumQuality, possibleDifficulty);
	}

	private int getPossibleDifficultyViaAttributes(IntTriple dice, IntTriple attributeTriple) {
		int possibleDifficulty1 = attributeTriple.getFirst() - dice.getFirst();
		int possibleDifficulty2 = attributeTriple.getSecond() - dice.getSecond();
		int possibleDifficulty3 = attributeTriple.getThird() - dice.getThird();

		return MathHelper.minimum(possibleDifficulty1, possibleDifficulty2, possibleDifficulty3);
	}

	private CheckResult buildSpecialSuccessResult(Check check, CheckOutcome outcome) {
		if (outcome == CheckOutcome.LUCKY_CHECK || outcome == CheckOutcome.SPECTACULAR_SUCCESS) {
			return new CheckResult(outcome, getMaximumQuality(check), null);
		} else {
			return new CheckResult(outcome, null, null);
		}
	}

	private int getMaximumQuality(Check check) {
		int value = check.getValue();
		return value > 0 ? value : getMinimumEffect(check);
	}

	private int getMinimumEffect(Check check) {
		return check.isMinimumEffect() ? 1 : 0;
	}

	private CheckOutcome determineSpecialCheckOutcome(Check check, IntTriple dice) {

		if (dice.hasThreeValuesOf(1)) {
			return CheckOutcome.SPECTACULAR_SUCCESS;
		}
		if (dice.hasTwoValuesOf(1)) {
			return CheckOutcome.LUCKY_CHECK;
		}
		if (dice.hasThreeValuesOf(20)) {
			return CheckOutcome.SPECTACULAR_FUMBLE;
		}
		if (isFumble(check, dice)) {
			return CheckOutcome.FUMBLE;
		}
		if (isSpruchhemmung(check, dice)) {
			return CheckOutcome.SPRUCHHEMMUNG;
		}

		return null;
	}

	boolean isFumble(Check check, IntTriple dice) {
		if (check.isFesteMatrix()) {
			return isFumbleWithFesteMatrix(dice);
		}
		if (check.isTollpatsch()) {
			return isFumbleWithTollpatsch(dice);
		}

		return dice.hasTwoValuesOf(20);
	}

	boolean isFumbleWithFesteMatrix(IntTriple dice) {
		boolean twoTwenties = dice.hasTwoValuesOf(20);
		boolean sumAtLeast58 = (dice.getFirst() + dice.getSecond() + dice.getThird()) >= 58;

		return twoTwenties && sumAtLeast58;
	}

	boolean isFumbleWithTollpatsch(IntTriple dice) {
		boolean firstAndSecond = dice.getFirst() >= 19 && dice.getSecond() >= 19;
		boolean firstAndThird = dice.getFirst() >= 19 && dice.getThird() >= 19;
		boolean secondAndThird = dice.getSecond() >= 19 && dice.getThird() >= 19;

		return firstAndSecond || firstAndThird || secondAndThird;
	}

	boolean isSpruchhemmung(Check check, IntTriple dice) {
		int first = dice.getFirst();
		int second = dice.getSecond();
		int third = dice.getThird();
		boolean twoDiceWithSameValue = first == second || second == third || first == third;

		return check.isSpruchhemmung() && twoDiceWithSameValue;
	}
}
